﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.ComponentModel;

namespace CFScript.PInvoke
{
    public class LINEGENERALINFO
    {
        public int dwManufacturerOffset;
        public int dwManufacturerSize;
        public int dwModelOffset;
        public int dwModelSize;
        public int dwNeededSize;
        public int dwRevisionOffset;
        public int dwRevisionSize;
        public int dwSerialNumberOffset;
        public int dwSerialNumberSize;
        public int dwSubscriberNumberOffset;
        public int dwSubscriberNumberSize;
        public int dwTotalSize;
        public int dwUsedSize;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct LINEEXTENSIONID
    {
        public IntPtr dwExtensionID0;
        public IntPtr dwExtensionID1;
        public IntPtr dwExtensionID2;
        public IntPtr dwExtensionID3;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct LINEINITIALIZEEXPARAMS
    {
        public uint dwTotalSize;
        public uint dwNeededSize;
        public uint dwUsedSize;
        public uint dwOptions;
        public System.IntPtr hEvent;
        public System.IntPtr hCompletionPort;
        public uint dwCompletionKey;
    }

    public class Device
    {
        private static Int32 METHOD_BUFFERED = 0;
        private static Int32 FILE_ANY_ACCESS = 0;
        private static Int32 FILE_DEVICE_HAL = 0x00000101;

        private const Int32 ERROR_NOT_SUPPORTED = 0x32;
        private const Int32 ERROR_INSUFFICIENT_BUFFER = 0x7A;

        private static Int32 IOCTL_HAL_GET_DEVICEID =
            ((FILE_DEVICE_HAL) << 16) | ((FILE_ANY_ACCESS) << 14)
            | ((21) << 2) | (METHOD_BUFFERED);

        [DllImport("coredll.dll", SetLastError = true)]
        private static extern bool KernelIoControl(Int32 dwIoControlCode,
            IntPtr lpInBuf, Int32 nInBufSize, byte[] lpOutBuf,
            Int32 nOutBufSize, ref Int32 lpBytesReturned);

        [DllImport("coredll")]
        public static extern int lineInitializeEx(out IntPtr lpm_hLineApp, IntPtr hInstance, IntPtr lpfnCallback, string lpszFriendlyAppName, out int lpdwNumDevs, ref int lpdwAPIVersion, ref LINEINITIALIZEEXPARAMS lpLineInitializeExParams);

        [DllImport("coredll")]
        public static extern int lineOpen(IntPtr m_hLineApp, int dwDeviceID, out IntPtr lphLine, int dwAPIVersion, int dwExtVersion, IntPtr dwCallbackInstance, int dwPrivileges, int dwMediaModes, IntPtr lpCallParams);

        [DllImport("coredll")]
        public static extern int lineNegotiateAPIVersion(IntPtr m_hLineApp, int dwDeviceID, int dwAPILowVersion, int dwAPIHighVersion, out int lpdwAPIVersion, out LINEEXTENSIONID lpExtensionId);

        [DllImport("cellcore")]
        public static extern int lineGetGeneralInfo(IntPtr hLine, byte[] bytes);

        [DllImport("cellcore")]
        public static extern int lineGetGeneralInfo(IntPtr hLine, ref LINEGENERALINFO lineGenerlInfo);

        [DllImport("coredll")]
        public static extern int lineClose(IntPtr hLine);

        [DllImport("coredll")]
        public static extern int lineShutdown(IntPtr m_hLineApp);

        public static string GetDeviceID()
        {
            // Initialize the output buffer to the size of a 
            // Win32 DEVICE_ID structure.
            byte[] outbuff = new byte[20];
            Int32 dwOutBytes;
            bool done = false;

            Int32 nBuffSize = outbuff.Length;

            // Set DEVICEID.dwSize to size of buffer.  Some platforms look at
            // this field rather than the nOutBufSize param of KernelIoControl
            // when determining if the buffer is large enough.
            BitConverter.GetBytes(nBuffSize).CopyTo(outbuff, 0);
            dwOutBytes = 0;

            // Loop until the device ID is retrieved or an error occurs.
            while (!done)
            {
                if (KernelIoControl(IOCTL_HAL_GET_DEVICEID, IntPtr.Zero,
                    0, outbuff, nBuffSize, ref dwOutBytes))
                {
                    done = true;
                }
                else
                {
                    int error = Marshal.GetLastWin32Error();
                    switch (error)
                    {
                        case ERROR_NOT_SUPPORTED:
                            throw new NotSupportedException(
                                "IOCTL_HAL_GET_DEVICEID is not supported on this device",
                                new Win32Exception(error));

                        case ERROR_INSUFFICIENT_BUFFER:

                            // The buffer is not big enough for the data.  The
                            // required size is in the first 4 bytes of the output
                            // buffer (DEVICE_ID.dwSize).
                            nBuffSize = BitConverter.ToInt32(outbuff, 0);
                            outbuff = new byte[nBuffSize];

                            // Set DEVICEID.dwSize to size of buffer.  Some
                            // platforms look at this field rather than the
                            // nOutBufSize param of KernelIoControl when
                            // determining if the buffer is large enough.
                            BitConverter.GetBytes(nBuffSize).CopyTo(outbuff, 0);
                            break;

                        default:
                            throw new Win32Exception(error, "Unexpected error");
                    }
                }
            }

            // Copy the elements of the DEVICE_ID structure.
            Int32 dwPresetIDOffset = BitConverter.ToInt32(outbuff, 0x4);
            Int32 dwPresetIDSize = BitConverter.ToInt32(outbuff, 0x8);
            Int32 dwPlatformIDOffset = BitConverter.ToInt32(outbuff, 0xc);
            Int32 dwPlatformIDSize = BitConverter.ToInt32(outbuff, 0x10);
            StringBuilder sb = new StringBuilder();

            for (int i = dwPresetIDOffset;
                i < dwPresetIDOffset + dwPresetIDSize; i++)
            {
                sb.Append(String.Format("{0:X2}", outbuff[i]));
            }

            sb.Append("-");

            for (int i = dwPlatformIDOffset;
                i < dwPlatformIDOffset + dwPlatformIDSize; i++)
            {
                sb.Append(String.Format("{0:X2}", outbuff[i]));
            }
            return sb.ToString();
        }

        private static void Get(out string manufacturer, out string model, out string revision, out string serialNumber, out string subsciberId)
        {
            IntPtr hLine;
            int dwNumDev;
            int num1 = 0x20000;
            LINEINITIALIZEEXPARAMS lineInitializeParams = new LINEINITIALIZEEXPARAMS();
            lineInitializeParams.dwTotalSize = (uint)Marshal.SizeOf(lineInitializeParams);
            lineInitializeParams.dwNeededSize = lineInitializeParams.dwTotalSize;
            lineInitializeParams.dwOptions = 2;
            lineInitializeParams.hEvent = IntPtr.Zero;
            lineInitializeParams.hCompletionPort = IntPtr.Zero;
            #region lineInitializeEx
            int result = lineInitializeEx(out hLine, IntPtr.Zero,
            IntPtr.Zero, null, out dwNumDev, ref num1, ref lineInitializeParams);
            if (result != 0)
            {
                throw new ApplicationException(string.Format("lineInitializeEx failed!\n\nError Code:{0}", result.ToString()));
            }
            #endregion
            #region lineNegotiateAPIVerison
            int version;
            int dwAPIVersionLow = 0x10004;
            int dwAPIVersionHigh = 0x20000;
            LINEEXTENSIONID lineExtensionID;
            result = lineNegotiateAPIVersion(hLine, 0, dwAPIVersionLow, dwAPIVersionHigh, out version, out lineExtensionID);
            if (result != 0)
            {
                throw new ApplicationException(string.Format("lineNegotiateAPIVersion failed!\n\nError Code: {0}", result.ToString()));
            }
            #endregion

            #region lineOpen
            IntPtr hLine2 = IntPtr.Zero;
            result = lineOpen(hLine, 0, out hLine2, version, 0, IntPtr.Zero, 0x00000002, 0x00000004, IntPtr.Zero);
            if (result != 0)
            {
                throw new ApplicationException(string.Format("lineNegotiateAPIVersion failed!\n\nError Code: {0}", result.ToString()));
            }
            #endregion

            #region lineGetGeneralInfo
            int structSize = Marshal.SizeOf(new LINEGENERALINFO());
            byte[] bytes = new byte[structSize];
            byte[] tmpBytes = BitConverter.GetBytes(structSize);

            for (int index = 0; index < tmpBytes.Length; index++)
            {
                bytes[index] = tmpBytes[index];
            }
            #endregion

            #region make initial query to retrieve necessary size
            result = lineGetGeneralInfo(hLine2, bytes);

            // get the needed size
            int neededSize = BitConverter.ToInt32(bytes, 4);

            // resize the array
            bytes = new byte[neededSize];

            // write out the new allocated size to the byte stream
            tmpBytes = BitConverter.GetBytes(neededSize);
            for (int index = 0; index < tmpBytes.Length; index++)
            {
                bytes[index] = tmpBytes[index];
            }

            // fetch the information with properly size buffer
            result = lineGetGeneralInfo(hLine2, bytes);

            if (result != 0)
            {
                throw new ApplicationException(Marshal.GetLastWin32Error().ToString());
            }

            #endregion

            #region actual data fetching
            int size;
            int offset;

            // manufacture
            size = BitConverter.ToInt32(bytes, 12);
            offset = BitConverter.ToInt32(bytes, 16);
            manufacturer = Encoding.Unicode.GetString(bytes, offset, size);
            manufacturer = manufacturer.Substring(0, manufacturer.IndexOf('\0'));

            // model
            size = BitConverter.ToInt32(bytes, 20);
            offset = BitConverter.ToInt32(bytes, 24);
            model = Encoding.Unicode.GetString(bytes, offset, size);
            model = model.Substring(0, model.IndexOf('\0'));

            // revision
            size = BitConverter.ToInt32(bytes, 28);
            offset = BitConverter.ToInt32(bytes, 32);
            revision = Encoding.Unicode.GetString(bytes, offset, size);
            revision = revision.Substring(0, revision.IndexOf('\0'));

            // serial number
            size = BitConverter.ToInt32(bytes, 36);
            offset = BitConverter.ToInt32(bytes, 40);
            serialNumber = Encoding.Unicode.GetString(bytes, offset, size);
            serialNumber = serialNumber.Substring(0, serialNumber.IndexOf('\0'));

            // subscriber id
            size = BitConverter.ToInt32(bytes, 44);
            offset = BitConverter.ToInt32(bytes, 48);
            subsciberId = Encoding.Unicode.GetString(bytes, offset, size);
            subsciberId = subsciberId.Substring(0, subsciberId.IndexOf('\0'));

            #endregion


            //tear down
            lineClose(hLine2);
            lineShutdown(hLine);

        }

        public static string GetDeviceIMEI()
        {
            string manufacturer;
            string model;
            string revision;
            string imsi;
            string imei = "N/A";

            try
            {
                Get(out manufacturer, out model, out revision, out imei, out imsi);
            }
            catch
            {
            }

            return imei;
        }

        public struct OS
        {
            public string name;
            public string wm_version;
            public string ce_version;
            public string akuVersion;
            public string fullName;
        }

        public struct Characteristics
        {
            public bool IsMotorola;
            public bool IsMC70;
            public bool IsMC75;
        }

        public struct ScreenProperties
        {
            public int Width;
            public int Height;
            public ScreenSize Resolution;
        }

        public enum ScreenSize
        {
            QVGA,
            VGA,
            UNKNOWN
        }

        public struct MemoryStatus
        {
            public UInt32 Length;
            public UInt32 MemoryLoad;
            public UInt32 TotalPhysical;
            public UInt32 AvailablePhysical;
            public UInt32 TotalPageFile;
            public UInt32 AvailablePageFile;
            public UInt32 TotalVirtual;
            public UInt32 AvailableVirtual;
        }

        internal string manufacturer = "Unknown";
        internal string family = "Unknown";
        internal string model = "Unknown";
        internal OS os = new OS() { name = "Unknown", ce_version = "Unknown", akuVersion = "Unknown", fullName = "Unknown" };
        internal string aku = "Unknown";
        internal bool emulator = false;
        internal List<string> supportedFrameworks = new List<string>();
        internal string runningFramework = "Unknown";
        internal string OEM_Info = "Unknown";
        internal Characteristics fast_attributes = new Characteristics() { IsMC70 = false, IsMC75 = false, IsMotorola = false };
        internal ScreenProperties screen;
        internal MemoryStatus memory;

        public string Manufacturer
        {
            get { return this.manufacturer; }
        }

        public string Family
        {
            get { return this.family; }
        }

        public string Model
        {
            get { return this.model; }
        }

        public OS OperatingSystem
        {
            get { return this.os; }
        }

        public bool IsEmulator
        {
            get { return this.emulator; }
        }

        public string OEM_INFO
        {
            get { return this.OEM_Info; }
            internal set { parseOEM_INFO(value); }
        }

        public List<string> SupportedFrameworks
        {
            get { return this.supportedFrameworks; }
        }

        public string RunningFramework
        {
            get { return this.runningFramework; }
        }

        public Characteristics FastAttributes
        {
            get { return this.fast_attributes; }
        }

        public ScreenProperties Screen
        {
            get { return this.screen; }
        }

        public MemoryStatus Memory
        {
            get { return this.memory; }
        }

        private void parseOEM_INFO(string value)
        {
            this.OEM_Info = value;
            value = value.ToLower();

            if (value.IndexOf("motorola") != -1 || value.IndexOf("symbol") != -1)
            {
                this.manufacturer = "Motorola";
                this.fast_attributes.IsMotorola = true;
            }

            if (value.IndexOf("mc70") != -1)
            {
                this.family = "MC70";
                this.fast_attributes.IsMC70 = true;
            }
            else if (value.IndexOf("mc75") != -1)
            {
                this.family = "MC75";
                this.fast_attributes.IsMC75 = true;
            }
        }
    }
}
